﻿using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Text;
using System.Xml.Linq;
using Appology.SugarSync.IO.Primitives;
using System.Threading.Tasks;

namespace Appology.SugarSync.IO
{
    public class SugarSyncDirectoryInfo : SugarSyncPathInfo
    {
        public SugarSyncDirectoryInfo(string name, SugarSyncAccount account, Uri refUri, Uri contentsUri)
            : base(account, refUri)
        {
            Name = name;
            ContentsUri = contentsUri;
        }

        private IEnumerable<SugarSyncPathInfo> _contents;

        public IEnumerable<SugarSyncPathInfo> Contents
        {
            get { return _contents; }
            set
            {
                if (_contents != value)
                {
                    _contents = value;
                    OnPropertyChanged("Contents");
                }
            }
        }

        private Uri _contentsUri;

        public Uri ContentsUri
        {
            get { return _contentsUri; }
            private set
            {
                if (_contentsUri != value)
                {
                    _contentsUri = value;
                    OnPropertyChanged("ContentsUri");
                }
            }
        }

        private DateTimeOffset _dateCreated;

        public DateTimeOffset DateCreated
        {
            get { return _dateCreated; }
            set
            {
                if (_dateCreated != value)
                {
                    _dateCreated = value;
                    OnPropertyChanged("DateCreated");
                }
            }
        }

        public void More()
        {
            // load next page of data...?
        }

        public async Task<SugarSyncDirectoryInfo> CreateDirectoryInfo(string folderName)
        {
            /*
            <folder>
                <displayName>subFolder</displayName>
            </folder>
            */

            if(string.IsNullOrEmpty(folderName))
            {
                return null;
            }

            XElement folder = XElement.Parse("<folder/>");
            XElement displayName = XElement.Parse("<displayName/>");

            displayName.Value = folderName;

            folder.Add(displayName);

            IsBusy = true;

            var webClient = await Account.GetWebClientAsync();

            using (var stream = await webClient.OpenWriteTaskAsync(RefUri, "POST"))
            {
                byte[] buffer = Encoding.UTF8.GetBytes(folder.ToString());

                stream.Write(buffer, 0, buffer.Length);
                stream.Flush();
                stream.Close();
            }

            await RefreshAsync();

            var directory = (from d in Contents.OfType<SugarSyncDirectoryInfo>()
                            where d.Name.Equals(folderName, StringComparison.InvariantCultureIgnoreCase)
                            select d).SingleOrDefault();

            IsBusy = false;

            return directory;
        }

        public async Task<SugarSyncFileInfo> CreateFileInfo(string fileName, string contentType)
        {
            /*
            <file>
                <displayName>mypicture</displayName>
                <mediaType>image/jpeg</mediaType>
            </file>
            */

            XElement file = XElement.Parse("<file/>");
            XElement displayName = XElement.Parse("<displayName/>");
            XElement mediaType = XElement.Parse("<mediaType/>");

            displayName.Value = fileName;
            mediaType.Value = contentType;

            file.Add(displayName);
            file.Add(mediaType);

            IsBusy = true;

            var webClient = await Account.GetWebClientAsync();

            var result = webClient.UploadStringTaskAsync(RefUri, "POST", file.ToString());

            var refUri = new Uri(webClient.ResponseHeaders["Location"], UriKind.Absolute);

            var fileInfo = new SugarSyncFileInfo(fileName, Account, refUri);

            IsBusy = false;

            return fileInfo;
        }

        public async Task Delete()
        {
            IsBusy = true;

            var webClient = await Account.GetWebClientAsync();

            using (var stream = await webClient.OpenWriteTaskAsync(RefUri, "DELETE"))
            {
                stream.Close();
            }

            IsBusy = false;
        }

        public async Task UpdateInfo(string newName)
        {
            /*
            <folder>
                <displayName>newFolderName</displayName>
            </folder>
            */

            IsBusy = true;

            var webClient = await Account.GetWebClientAsync();

            var file = XElement.Parse("<folder/>");

            var displayName = XElement.Parse("<displayName/>");

            displayName.Value = newName;

            file.Add(displayName);

            using (var stream = await webClient.OpenWriteTaskAsync(RefUri, "PUT"))
            {
                var buffer = Encoding.UTF8.GetBytes(file.ToString());
                stream.Write(buffer, 0, buffer.Length);
                stream.Flush();
                stream.Close();
            }

            IsBusy = false;
        }

        private bool _isBusy;

        public override bool IsBusy
        {
            get { return _isBusy; }
            set
            {
                if (_isBusy != value)
                {
                    _isBusy = value;
                    OnPropertyChanged("IsBusy");
                }
            }
        }

        public async override Task RefreshAsync()
        {
            Contents = null;
            
            IsBusy = true;

            if(RefUri != null)
            {
                await RefreshRef();
            }

            if (ContentsUri != null)
            {
                await RefreshContents();   
            }

            IsBusy = false;
        }

        private async Task RefreshRef()
        {
            var result = await Account.GetXmlAsync(RefUri);

            /*
                <folder>
                   <displayName>folder1</displayName>
                   <timeCreated>2009-06-25T11:31:05.000-07:00</timeCreated>
                   <parent>https://api.sugarsync.com/folder/myfolderId</parent>
                   <collections>https://api.sugarsync.com/folder/myfolderId/contents?type=folder
                   </collections>
                   <files>https://api.sugarsync.com/folder/myfolderId/contents?type=file</files>
                   <contents>https://api.sugarsync.com/folder/myfolderId/contents</contents>
                </folder>
                */

            Name = result.Element("displayName").Value;

            var dateCreatedElement = result.Element("timeCreated");

            if (dateCreatedElement != null)
            {
                DateCreated = DateTimeOffset.ParseExact(dateCreatedElement.Value, "yyyy-MM-ddTHH:mm:ss.fffzzz", CultureInfo.InvariantCulture);
            }

            var parentElement = result.Element("parent");

            if (parentElement != null)
            {
                ParentRefUri = new Uri(parentElement.Value, UriKind.Absolute);
            }

            ContentsUri = new Uri(result.Element("contents").Value, UriKind.Absolute);
        }

        private async Task RefreshContents()
        {
            var result = await Account.GetXmlAsync(ContentsUri);

            /*
                <collectionContents start="0" hasMore="true" end="1">
                   <collection type="syncFolder">
                     <displayName>folder1</displayName>
                     <ref>https://api.sugarsync.com/folder/asdf/ref>
                     <contents>https://api.sugarsync.com/folder/asdf/contents</contents>
                   </collection>
                   <collection type="syncFolder">
                     <displayName>folder2</displayName>
                     <ref>https://api.sugarsync.com/folder/foobar/ref>
                     <contents>https://api.sugarsync.com/folder/foobar/contents</contents>
                   </collection>
                </collectionContents>
                */
            var contents = new List<SugarSyncPathInfo>();

            var directories = from e in result.Elements()
                              where e.Name.LocalName == "collection"
                              select new SugarSyncDirectoryInfo(e.Element("displayName").Value, Account, new Uri(e.Element("ref").Value, UriKind.Absolute), null);

            var files = from e in result.Elements()
                        where e.Name.LocalName == "file"
                        select new SugarSyncFileInfo(e.Element("displayName").Value, Account, new Uri(e.Element("ref").Value, UriKind.Absolute));

            contents.AddRange(directories.Cast<SugarSyncPathInfo>());
            contents.AddRange(files.Cast<SugarSyncPathInfo>());

            Contents = contents;

            LastUpdated = DateTimeOffset.Now;
        }

        public static async Task<SugarSyncDirectoryInfo> FindDirectory(string path, SugarSyncAccount account)
        {
            return await InternalCreateDirectory(path, false, account);
        }

        public static async Task<SugarSyncDirectoryInfo> CreateDirectory(string path, bool recursive, SugarSyncAccount account)
        {
            return await InternalCreateDirectory(path, recursive, account);
        }

        private static async Task<SugarSyncDirectoryInfo> InternalCreateDirectory(string path, bool recursive, SugarSyncAccount account)
        {
            /*
             * example path...
             * /Sync Folders/Magic Briefcase/Application Data/Appology
             * 
             */

            // TODO: inspect path for read-only directories...

            var pathParts = new Queue<string>(path.Split(new []{'/','\\'}));

            bool rootProcessed = false;
            SugarSyncDirectoryInfo currentDirectoryInfo = null;

            while (pathParts.Count > 0)
            {
                var pathPart = pathParts.Dequeue();

                if (!rootProcessed && pathPart != null && pathPart.Trim() == string.Empty)
                {
                    #region Process Root and Immediate Child
                    var root = new SugarSyncStorageRoot("root", account);

                    await root.RefreshAsync();

                    // root is read-only, so attempt to match next
                    // path part to an existing path in the root

                    var nextPathPart = pathParts.Dequeue();

                    foreach (var directoryInfo in root.Contents.OfType<SugarSyncDirectoryInfo>())
                    {
                        if (directoryInfo.Name.Equals(nextPathPart, StringComparison.InvariantCultureIgnoreCase))
                        {
                            currentDirectoryInfo = directoryInfo;
                            rootProcessed = true;
                            break;
                        }
                    }

                    if (currentDirectoryInfo == null)
                    {
                        throw new Exception(string.Format("Could not find directory named \"{0}\" under account root.", nextPathPart));
                    }
                    #endregion
                }
                else if (!rootProcessed)
                {
                    throw new Exception(string.Format("Relative paths are not supported. [Path=\"{0}\"]", path));
                }
                else if (pathPart != null && pathPart.Trim() != string.Empty)
                {
                    #region Process Current Directory

                    SugarSyncDirectoryInfo nextDirectoryInfo = null;

                    await currentDirectoryInfo.RefreshAsync();

                    foreach (var pathInfo in currentDirectoryInfo.Contents.OfType<SugarSyncDirectoryInfo>())
                    {
                        if (pathInfo.Name.Equals(pathPart, StringComparison.InvariantCultureIgnoreCase))
                        {
                            nextDirectoryInfo = pathInfo;
                            break;
                        }
                    }

                    if (nextDirectoryInfo == null)
                    {
                        if (recursive || pathParts.Count == 0)
                        {
                            // create directory
                            currentDirectoryInfo = await currentDirectoryInfo.CreateDirectoryInfo(pathPart);
                        }
                        else
                        {
                            throw new Exception(string.Format("Could not find part of path \"{0}\".", path));
                        }
                    }
                    else
                    {
                        currentDirectoryInfo = nextDirectoryInfo;
                    }

                    #endregion
                }
            }

            return currentDirectoryInfo;
        }
    }
}